//============================================================================
// Copyright (C) 2009-2013 Brett R. Jones 
// All Rights Reserved
//
// You may redistribute and/or modify for non commercial and commercial uses 
// provided this copyright notice remains in place and is not modified
//
// This code is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// brettjones1900@gmail.com
// http://www.p2panarchy.com
//============================================================================

#include <string.h>
#include <memory.h>
#include "RcSysSktMgrLib.h"
#include "RcSysSktBroadcast.h"
#include <RcSysPktLib/RcSysPktDie.h>
#include <VxUtilLib/VxSktUtil.h>
#include <VxUtilLib/VxGlobals.h>

void  RcSysSktBroadcastTxThread( void * pvContext )
{
	log_msg( LOG_INFO, "RcSysSktBroadcastTxThread starting\n" );
	VxThread * poThread = (VxThread *)pvContext;
	poThread->SetIsThreadRunning( true );
	RcSysSktBroadcast * poSkt = (RcSysSktBroadcast *)poThread->GetUserParam();

	U32					u32TimeInterval = poSkt->m_u32TimeIntervalSeconds;
	int					iSleepTime = ( u32TimeInterval * 1000 ) >> 2;
	if( iSleepTime > 2000 )
	{
		// thread need to wake up at least every couple of seconds
		// so it can exit if abort is called
		iSleepTime = 2000;
	}

	time_t				tTimeLastBroadcast = 0;
	RCODE				rc = 0;

	//log_msg( LOG_INFO, "RcSysSktBroadcastTxThread calling onPktAnnUpdated\n" );
	poSkt->onPktAnnUpdated( poSkt->m_oPktAnn );

	//log_msg( LOG_INFO, "BRcSysSktBroadcastTxThread starting broadcast loop\n" );
	while(	( false == poThread->isAborted() ) && 
			( INVALID_SOCKET != poSkt->m_oSocket ) )
	{
//		log_msg( LOG_INFO, "BRcSysSktBroadcastTxThread starting checking time interval %d, current %d last %d\n",
//					u32TimeInterval, g_u32SysTimeSec, tTimeLastBroadcast  );
		if( (g_u32SysTimeSec - tTimeLastBroadcast) > u32TimeInterval )
		{
			tTimeLastBroadcast = g_u32SysTimeSec;
//			log_msg( LOG_INFO, "BRcSysSktBroadcastTxThread Locking\n" );
			//log_msg( LOG_INFO, "Broadcast skt %d \n", poSkt );
			// time to send
			poSkt->DoLock();
			//log_msg( LOG_INFO, "RcSysSktBroadcastTxThread Broadcasting\n" );
			rc = poSkt->BroadcastData(	(const char *)&poSkt->m_oPktAnnEncrypted,		// data to send
										poSkt->m_iBroadcastDataLen, 			// data length
										poSkt->m_u16UdpPort );
			//log_msg( LOG_INFO, "BRcSysSktBroadcastTxThread Unlocking\n" );
			poSkt->DoUnlock();
//			log_msg( LOG_INFO, "Broadcasted len %d port %d\n", poSkt->m_iBroadcastDataLen, poSkt->m_u16UdpPort );
		}
		VxSleep( iSleepTime );
	}

	log_msg( LOG_INFO, "Broadcast thread exiting\n" );
	//! Thread calls this just before exit
	poThread->ThreadAboutToExit();
}

//============================================================================
//=== constructor ===//
RcSysSktBroadcast::RcSysSktBroadcast()
	: m_u32TimeIntervalSeconds(0)
	, m_u16UdpPort(0)
{
}

//============================================================================
//=== destructor ===//
RcSysSktBroadcast::~RcSysSktBroadcast()
{
}

//============================================================================
void RcSysSktBroadcast::generateUdpBroadcastKeys( void )
{
	int iVal = 0;
	const char * pNetName = VxGetRogueNetworkName();

	unsigned int iLen = (unsigned int)strlen( pNetName );
	for( unsigned int i = 0; i < iLen; i++ )
	{
		iVal += pNetName[ i ];
	}
	int iIdx = iVal & 0x7f;
	//log_msg( LOG_INFO, "RcSysSktBroadcast::StartBroadcast: importing encryption key\n" );
	// set receive decryption key
	this->m_oRxKey.ImportKey( GetVxSktRandData( iIdx ), 16 );

	// dont set the transmit key
	// transmit thread will use m_oRxKeyCopy to encrypt the announce pkt once
	// then send without having to encrypt each time

	m_oRxKeyCopy.ImportKey( &m_oRxKey );	
}

//============================================================================
//! Startup
RCODE RcSysSktBroadcast::StartBroadcast(	VxInetAddress&		oLclIp,
											RcSysPktAnnounce *  poPktAnn,				// packet announce to send out
											U16					u16Port,				// port to broadcast to
											U32					u32TimeIntervalSeconds,	// time interval between broadcasts
											const char *		pBroadcastIp )
{	
	//log_msg( LOG_INFO, "RcSysSktBroadcast::StartBroadcast: start skt %d\n", m_iSktId );
	m_u16UdpPort = u16Port;
	if( IsConnected() )
	{
		log_msg( LOG_ERROR, "RcSysSktBroadcast::StartBroadcast: closing connected skt %s\n", m_iSktId );
		CloseSkt();
	}
	//log_msg( LOG_INFO, "RcSysSktBroadcast::StartBroadcast: generating encryption key\n" );
	m_u32TimeIntervalSeconds = u32TimeIntervalSeconds;

	onPktAnnUpdated(*poPktAnn);

	RCODE rc = UdpBroadcastOpen(	m_oLclIp,
									u16Port,			// port to listen on
									pBroadcastIp);		// if not null then use ip other than default host ip
	if( rc )
	{
		log_msg( 0, "RcSysSktBroadcast::StartBroadcast: skt %d could not open port %d error %s\n", m_iSktId, u16Port, DescribeSktErr( rc ) );
		return rc;
	}
	//log_msg( 0, "RcSysSktBroadcast::StartBroacast starting thread skt %d\n", this->m_iSktId );
	rc = m_oTxThread.StartThread( (VX_THREAD_FUNCTION_T)RcSysSktBroadcastTxThread, this );
	return rc;
}

//============================================================================
//! Shutdown
void RcSysSktBroadcast::StopBroadcast( void )
{
	log_msg( 0, "RcSysSktBroadcast::StopBroadcast skt %d\n", this->m_iSktId );
	if( m_oTxThread.IsThreadCreated() )
	{
		m_oTxThread.Abort( true );
		// send out I'm dieing packet
		RcSysPktDie		oPkt;
		U16 u16PktLen = oPkt.m_u16PktLen;
		VxSymEncrypt( &m_oRxKey, (char *)&oPkt, u16PktLen );
		BroadcastData(		(const char *)&oPkt,
							u16PktLen );
		CloseSkt();
		m_oTxThread.KillThread();
	}
};

//============================================================================
//! called after PktAnnounce has changed
void RcSysSktBroadcast::onPktAnnUpdated( RcSysPktAnnounce& oPktAnn )
{
	DoLock();

	generateUdpBroadcastKeys();

	// get copy of announce pkt and encryption key
	memcpy( &m_oPktAnn, &oPktAnn, sizeof( RcSysPktAnnounce ) ); 
	m_iBroadcastDataLen = m_oPktAnn.m_u16PktLen;
	vx_assert( sizeof( RcSysPktAnnounce ) == m_iBroadcastDataLen );
	memcpy( &m_oPktAnnEncrypted, &m_oPktAnn, m_iBroadcastDataLen );

	// set our permission level to guests
	m_oPktAnnEncrypted.setMyFriendshipToHim(eFriendStateGuest);
	m_oPktAnnEncrypted.setHisFriendshipToMe(eFriendStateGuest);

	VxKey				oKey;
	oKey.ImportKey( &m_oRxKeyCopy );

	vx_assert( oKey.IsKeySet() );
	vx_assert( m_oPktAnnEncrypted.isValidPktAnn() );

	// encrypt our pkt
	VxSymEncrypt( &oKey, (char *)&m_oPktAnnEncrypted, m_iBroadcastDataLen );

	DoUnlock();
}
